/**
 * (1) Union
 *
 * type1 | type2 | type3 ...
 *
 * 使用 Union 类型时，常常需要 type narrowing
 */
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void, fly?: () => void };

function literalUnion() {
}

function varibaleUnion() {
  /**
   * 1.变量声明
   */

  /**
   * 2.变量初始化
   */

  /**
   * 3.变量赋值
   */

  /**
   * 4.变量使用
   */
}

// Assigment narrowing
function exampleWithAssigmentNarrowing() {
  let x: string | number | boolean;

  x = Math.random() < 0.5;
  // x: boolean
  console.log(x);

  if (Math.random() < 0.5) {
    x = 'hello';
    // x: string
    console.log(x);
  } else {
    x = 100;
    // x: number
    console.log(x);
  }

  // x: string | number
  return x;
}

// Control flow narrowing
// typeof type guard narrowing
// typeof value => 'number', 'bigint', 'string', 'boolean', 'symbol', 'function', 'object', 'undefined'
function printAllWithTypeofTypeGuardNarrowing(strs: string | string[] | null) {
  // strs: string | string[] | null
  if (typeof strs === 'object') {
    // strs: string[] | null, because typeof null === 'object'
    for (const s of strs) { // compile is error, because strs maybe null
      console.log(s);
    }
  } else if (typeof strs === 'string') {
    // strs: string
    console.log(strs);
  }
}

// User-defined type guard narrowing
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function exampleWithUserDefinedTypeGuardNarrowing(pet: Fish | Bird) {
  // pet: Fish | Bird
  if (isFish(pet)) {
    // pet: Fish
    pet.swim();
  } else {
    // pet: Bird
    pet.fly();
  }
}

// truthiness narrowing
// 0, 0n, NaN, '', null, undefined => false
// other => false
function printAllWithTruthinessNarrowing(strs: string | string[] | null) {
  // strs: string | string[] | null
  if (strs && typeof strs === 'object') {
    // strs: string[]
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === 'string') {
    // strs: string
    console.log(strs);
  }
}

// equality narrowing
// ==, !=, ===, !==
function printAllWithEqualityNarrowing(strs: string | string[] | null) {
  // strs: string | string[] | null
  if (strs !== null) {
    // strs: string | string[]
    if (typeof strs === 'object') {
      // strs: string[]
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === 'string') {
      // strs: string
      console.log(strs);
    }
  }
}

function exampleWithEqualityNarrowing(x: string | number, y: string | boolean) {
  // x: string | number, y: string | boolean
  if (x === y) {
    // x: string, y: string
    x.toUpperCase();
    y.toLowerCase();
  } else {
    // x: string | number, y: string | boolean
    console.log(x);
    console.log(y);
  }
}

// instanceof narrowing
function loggerWithInstanceofNarrowing(x: string | Date) {
  // x: string | Date
  if (x instanceof Date) {
    // x: Date
    console.log(x.toUTCString());
  } else {
    // x: string
    console.log(x.toUpperCase());
  }
}

// in operator narrowing
function moveWithInOperatorNarrowing(animal: Fish | Bird | Human) {
  // animal: Fish | Bird | Human
  if ('swim' in animal) {
    // animal: Fish | Human
    return animal.swim(); // compile is error，because animal.swim maybe undefined
  }
  // animal: Bird | Human
  return animal.fly(); // compile is error，because animal.swim maybe undefined
}
